package com.maliu.utils
{
	import flash.utils.Dictionary;

	/**
	 * To successfully store and retrieve (key->value) mapping from a HashMap.
	 * HashMap accept any type of object to be the key: number, string, Object etc... 
	 * But it is only get fast accessing with string type keys. Others are slow.
	 * <p>
	 * ----------------------------------------------------------
	 * This example creates a HashMap of friends. It uses the number of the friends as keys:
	 * <listing>
	 *     function person(name,age,sex){
	 *         this.name=name;
	 *         this.age=age;
	 *         this.sex=sex;
	 *     }
	 *     var friends = new HashMap();
	 *     friends.put("one", new person("iiley",21,"M"));
	 *     friends.put("two", new person("gothic man",22,"M"));
	 *     friends.put("three", new person("rock girl",19,"F"));
	 * </listing>
	 * </p>
	 * <p>To retrieve a friends, use the following code:
	 *
	 * <listing>
	 *     var thisperson = friends.get("two");
	 *     if (thisperson != null) {
	 *         trace("two name is "+thisperson.name);
	 *         trace("two age is "+thisperson.age);
	 *         trace("two sex is "+thisperson.sex);
	 *     }else{
	 *         trace("two is not in friends!");
	 *     }
	 * </listing>
	 *</p>
	 * @author iiley
	 * modify by maliu
	 */
	public class HashMap
	{
		private var m_length:int;
		private var m_contentList:Dictionary;

		public function HashMap()
		{
			m_length = 0;
			m_contentList = new Dictionary();
		}

		// -------------------public methods--------------------
		/**
		 * Returns the number of keys in this HashMap.
		 */
		public function length():int
		{
			return m_length;
		}

		/**
		 * Returns if this HashMap maps no keys to values.
		 */
		public function isEmpty():Boolean
		{
			return (m_length == 0);
		}

		/**
		 * Returns an Array of the keys in this HashMap.
		 */
		public function getKeys():Array
		{
			var temp:Array = new Array(m_length);
			var index:int = 0;
			for (var i:* in m_contentList)
			{
				temp[index] = i;
				index++;
			}
			return temp;
		}

		/**
		 * Call func(key) for each key.
		 * @param func the function to call
		 */
		public function eachKey(func:Function):void
		{
			for (var i:* in m_contentList)
			{
				func(i);
			}
		}

		/**
		 * Call func(value) for each value.
		 * @param func the function to call
		 */
		public function eachValue(func:Function):void
		{
			for each (var i:* in m_contentList)
			{
				func(i);
			}
		}

		/**
		 * Returns an Array of the values in this HashMap.
		 */
		public function getValues():Array
		{
			var temp:Array = new Array(m_length);
			var index:int = 0;
			for each (var i:* in m_contentList)
			{
				temp[index] = i;
				index++;
			}
			return temp;
		}

		/**
		 * Tests if some key maps into the specified value in this HashMap. 
		 * This operation is more expensive than the containsKey method.
		 */
		public function containsValue(value:*):Boolean
		{
			for each (var i:* in m_contentList)
			{
				if (i === value)
				{
					return true;
				}
			}
			return false;
		}

		/**
		 * Tests if the specified object is a key in this HashMap.
		 * This operation is very fast if it is a string.
		 * @param   key   The key whose presence in this map is to be tested
		 * @return <tt>true</tt> if this map contains a mapping for the specified
		 */
		public function containsKey(key:*):Boolean
		{
			if (m_contentList[key] != undefined)
			{
				return true;
			}
			return false;
		}

		/**
		 * Returns the value to which the specified key is mapped in this HashMap.
		 * Return null if the key is not mapped to any value in this HashMap.
		 * This operation is very fast if the key is a string.
		 * @param   key the key whose associated value is to be returned.
		 * @return  the value to which this map maps the specified key, or
		 *          <tt>null</tt> if the map contains no mapping for this key
		 *           or it is null value originally.
		 */
		public function getValue(key:*):*
		{
			var value:* = m_contentList[key];
			if (value !== undefined)
			{
				return value;
			}
			return null;
		}
		
		/**
		 * 通过键值返回键，若只有一个匹配时，返回该匹配键，若有多个匹配时，返回匹配键的数组
		 */
		public function getKey(_value:*):*
		{
			var t_resultList:Array = [];
			for(var k:* in m_contentList)
			{
				if(m_contentList[k] == _value)
				{
					t_resultList.push(k);
				}
			}
			if(t_resultList.length == 1)
			{
				return k;
			}
			if(t_resultList.length > 1)
			{
				return t_resultList;
			}
		}

//		/**
//		 * Same functionity method with different name to <code>get</code>.
//		 * 
//		 * @param   key the key whose associated value is to be returned.
//		 * @return  the value to which this map maps the specified key, or
//		 *          <tt>null</tt> if the map contains no mapping for this key
//		 *           or it is null value originally.
//		 */
//		public function getValue(key:*):*
//		{
//			return get(key);
//		}

		/**
		 * Associates the specified value with the specified key in this map. 
		 * If the map previously contained a mapping for this key, the old value is replaced. 
		 * If value is null, means remove the key from the map.
		 * @param key key with which the specified value is to be associated.
		 * @param value value to be associated with the specified key. null to remove the key.
		 * @return previous value associated with specified key, or <tt>null</tt>
		 *	       if there was no mapping for key.  A <tt>null</tt> return can
		 *	       also indicate that the HashMap previously associated
		 *	       <tt>null</tt> with the specified key.
		 */
		public function put(key:*, value:*):*
		{
			if (key == null)
			{
				throw new ArgumentError("cannot put a value with undefined or null key!");
				return undefined;
			}
			else if (value == null)
			{
				return remove(key);
			}
			else
			{
				var exist:Boolean = containsKey(key);
				if (!exist)
				{
					m_length++;
				}
				var oldValue:* = this.getValue(key);
				m_contentList[key] = value;
				return oldValue;
			}
		}

		/**
		 * Removes the mapping for this key from this map if present.
		 *
		 * @param  key key whose mapping is to be removed from the map.
		 * @return previous value associated with specified key, or <tt>null</tt>
		 *	       if there was no mapping for key.  A <tt>null</tt> return can
		 *	       also indicate that the map previously associated <tt>null</tt>
		 *	       with the specified key.
		 */
		public function remove(key:*):*
		{
			var exist:Boolean = containsKey(key);
			if (!exist)
			{
				return null;
			}
			var temp:* = m_contentList[key];
			delete m_contentList[key];
			m_length--;
			return temp;
		}

		/**
		 * Clears this HashMap so that it contains no keys no values.
		 */
		public function clear():void
		{
			m_length = 0;
			m_contentList = new Dictionary();
		}

		/**
		 * Return a same copy of HashMap object
		 */
		public function clone():HashMap
		{
			var temp:HashMap = new HashMap();
			for (var i:* in m_contentList)
			{
				temp.put(i, m_contentList[i]);
			}
			return temp;
		}

		public function toString():String
		{
			var ks:Array = getKeys();
			var vs:Array = getValues();
			var temp:String = "HashMap Content:\n";
			for (var i:int = 0; i < ks.length; i++)
			{
				temp += ks[i] + " -> " + vs[i] + "\n";
			}
			return temp;
		}
	}
}
